<?php
/**
 * This PHP-Class should only read a iCal-File (*.ics), parse it and give an
* array with its content.
*
* PHP Version 5
*
* @category Parser
* @package  Ics-parser
* @author   Martin Thoma <info@martin-thoma.de>
* @license  http://www.opensource.org/licenses/mit-license.php  MIT License
* @version  SVN: <svn_id>
* @link     http://code.google.com/p/ics-parser/
* @example  $ical = new ical('MyCal.ics');
*           print_r( $ical->get_event_array() );
*/


/**
 * This is the iCal-class
 *
 * @category Parser
 * @package  Ics-parser
 * @author   Martin Thoma <info@martin-thoma.de>
 * @license  http://www.opensource.org/licenses/mit-license.php  MIT License
 * @link     http://code.google.com/p/ics-parser/
 *
 * @param {string} filename The name of the file which should be parsed
 * @constructor
*/
class ICalReader
{
	/* How many ToDos are in this ical? */
	public  /** @type {int} */ $todo_count = 0;

	/* How many events are in this ical? */
	public  /** @type {int} */ $event_count = 0;

	/* The parsed calendar */
	public /** @type {Array} */ $cal;

	/* Which keyword has been added to cal at last? */
	private /** @type {string} */ $_lastKeyWord;

	function mb_str_replace($search, $replace, $subject) {
		if (is_array($subject)) {
			foreach ($subject as $key => $val) {
				$subject[$key] = mb_str_replace((string)$search, $replace, $subject[$key]);
			}
			return $subject;
		}
		$pattern = '/(?:'.implode('|', array_map(create_function('$match', 'return preg_quote($match[0], "/");'), (array)$search)).')/u';
		if (is_array($search)) {
			if (is_array($replace)) {
				$len = min(count($search), count($replace));
				$table = array_combine(array_slice($search, 0, $len), array_slice($replace, 0, $len));
				$f = create_function('$match', '$table = '.var_export($table, true).'; return array_key_exists($match[0], $table) ? $table[$match[0]] : $match[0];');
				$subject = preg_replace_callback($pattern, $f, $subject);
				return $subject;
			}
		}
		$subject = preg_replace($pattern, (string)$replace, $subject);
		return $subject;
	}
	/**
	 * Creates the iCal-Object
	 *
	 * @param {string} $filename The path to the iCal-file
	 *
	 * @return Object The iCal-Object
	 */
	public function __construct($filename,$type='file')
	{
		if (!$filename) {
			return false;
		}
		if($type=='file'){
			$tmp = file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
		}else{
			$tmp = explode("\n",$filename);
		}
		if (stristr($tmp[0], 'BEGIN:VCALENDAR') === false) {
			return false;
		} else {
			$lines=array();
			$dx=0;
			foreach($tmp as $k=>$v){
				$v = $this->mb_str_replace("\n","",str_replace("\r","",$v));
				if(substr($v,0,1)=="\t" || substr($v,0,1)==" "){
					$lines[$dx] .= trim(trim(trim($v," "),"\t")," ");
				}else{
					$dx++;
					$lines[$dx]=$v;
				}
			}
			foreach ($lines as $line) {
				$line = trim($line);
				$add  = $this->keyValueFromString($line);
				if ($add === false) {
					$this->addCalendarComponentWithKeyAndValue($type, false, $line);
					continue;
				}

				list($keyword, $value) = $add;

				switch ($line) {
					// http://www.kanzaki.com/docs/ical/vtodo.html
					case "BEGIN:VTODO":
						$this->todo_count++;
						$type = "VTODO";
						break;

						// http://www.kanzaki.com/docs/ical/vevent.html
					case "BEGIN:VEVENT":
						//echo "vevent gematcht";
						$this->event_count++;
						$type = "VEVENT";
						break;

						//all other special strings
					case "BEGIN:VCALENDAR":
					case "BEGIN:DAYLIGHT":
						// http://www.kanzaki.com/docs/ical/vtimezone.html
					case "BEGIN:VTIMEZONE":
					case "BEGIN:STANDARD":
						$type = $value;
						break;
					case "END:VTODO": // end special text - goto VCALENDAR key
					case "END:VEVENT":
					case "END:VCALENDAR":
					case "END:DAYLIGHT":
					case "END:VTIMEZONE":
					case "END:STANDARD":
						$type = "VCALENDAR";
						break;
					default:
						$this->addCalendarComponentWithKeyAndValue($type,
						$keyword,
						$value);
						break;
				}
			}
			return $this->cal;
		}
	}

	/**
	 * Add to $this->ical array one value and key.
	 *
	 * @param {string} $component This could be VTODO, VEVENT, VCALENDAR, ...
	 * @param {string} $keyword   The keyword, for example DTSTART
	 * @param {string} $value     The value, for example 20110105T090000Z
	 *
	 * @return {None}
	 */
	public function addCalendarComponentWithKeyAndValue($component,
			$keyword,
			$value)
	{
		if ($keyword == false) {
			$keyword = $this->last_keyword;
			switch ($component) {
				case 'VEVENT':
					$value = $this->cal[$component][$this->event_count - 1]
					[$keyword].$value;
					break;
				case 'VTODO' :
					$value = $this->cal[$component][$this->todo_count - 1]
					[$keyword].$value;
					break;
			}
		}

		if (stristr($keyword, "DTSTART") or stristr($keyword, "DTEND")) {
			$keyword = explode(";", $keyword);
			$keyword = $keyword[0];
		}

		switch ($component) {
			case "VTODO":
				$this->cal[$component][$this->todo_count - 1][$keyword] = $value;
				//$this->cal[$component][$this->todo_count]['Unix'] = $unixtime;
				break;
			case "VEVENT":
				$this->cal[$component][$this->event_count - 1][$keyword] = $value;
				break;
			default:
				$this->cal[$component][$keyword] = $value;
				break;
		}
		$this->last_keyword = $keyword;
	}

	/**
	 * Get a key-value pair of a string.
	 *
	 * @param {string} $text which is like "VCALENDAR:Begin" or "LOCATION:"
	 *
	 * @return {array} array("VCALENDAR", "Begin")
	 */
	public function keyValueFromString($text)
	{
		preg_match("/([^:]+)[:]([\w\W]*)/", $text, $matches);
		if (count($matches) == 0) {
			return false;
		}
		$matches = array_splice($matches, 1, 2);
		return $matches;
	}

	/**
	 * Return Unix timestamp from ical date time format
	 *
	 * @param {string} $icalDate A Date in the format YYYYMMDD[T]HHMMSS[Z] or
	 *                           YYYYMMDD[T]HHMMSS
	 *
	 * @return {int}
	 */
	public function iCalDateToUnixTimestamp($icalDate)
	{
		$icalDate = str_replace('T', '', $icalDate);
		$icalDate = str_replace('Z', '', $icalDate);

		$pattern  = '/([0-9]{4})';   // 1: YYYY
		$pattern .= '([0-9]{2})';    // 2: MM
		$pattern .= '([0-9]{2})';    // 3: DD
		$pattern .= '([0-9]{0,2})';  // 4: HH
		$pattern .= '([0-9]{0,2})';  // 5: MM
		$pattern .= '([0-9]{0,2})/'; // 6: SS
		preg_match($pattern, $icalDate, $date);

		// Unix timestamp can't represent dates before 1970
		if ($date[1] <= 1970) {
			return false;
		}
		$timestamp = mktime((int)$date[4],
				(int)$date[5],
				(int)$date[6],
				(int)$date[2],
				(int)$date[3],
				(int)$date[1]);
		return  $timestamp;
	}

	/**
	 * Returns an array of arrays with all events. Every event is an associative
	 * array and each property is an element it.
	 *
	 * @return {array}
	 */
	public function events()
	{
		$array = $this->cal;
		return $array['VEVENT'];
	}

	/**
	 * Returns a boolean value whether thr current calendar has events or not
	 *
	 * @return {boolean}
	 */
	public function hasEvents()
	{
		return ( count($this->events()) > 0 ? true : false );
	}

	/**
	 * Returns a boolean value whether thr current calendar has events or not
	 *
	 * @param {boolean} $rangeStart Either true or false
	 * @param {boolean} $rangeEnd   Either true or false
	 *
	 * @return {boolean}
	 */
	public function eventsFromRange($rangeStart = false, $rangeEnd = false)
	{
		$extendedEvents = array();

		if (!$rangeStart) {
			$rangeStart = new DateTime();
		}
		if (!$rangeEnd) {
			$rangeEnd = new DateTime('2038/12/31');
		}

		$rangeStart = $rangeStart->format('U');
		$rangeEnd   = $rangeEnd->format('U');

		$events = $this->sortEventsWithOrder($this->events(), SORT_ASC);

		// loop through all events by adding two new elements
		foreach ($events as $anEvent) {
			$timestamp = $this->iCalDateToUnixTimestamp($anEvent['DTSTART']);
			if ($timestamp >= $rangeStart && $timestamp <= $rangeEnd) {
				$extendedEvents[] = $anEvent;
			}
		}

		return $extendedEvents;
	}

	/**
	 * Returns a boolean value whether thr current calendar has events or not
	 *
	 * @param {array} $events    An array with events.
	 * @param {array} $sortOrder Either SORT_ASC, SORT_DESC, SORT_REGULAR,
	 *                           SORT_NUMERIC, SORT_STRING
	 *
	 * @return {boolean}
	 */
	public function sortEventsWithOrder($events, $sortOrder = SORT_ASC)
	{
		$extendedEvents = array();

		// loop through all events by adding two new elements
		foreach ($events as $anEvent) {
			if (!array_key_exists('UNIX_TIMESTAMP', $anEvent)) {
				$anEvent['UNIX_TIMESTAMP'] =
				$this->iCalDateToUnixTimestamp($anEvent['DTSTART']);
			}

			if (!array_key_exists('REAL_DATETIME', $anEvent)) {
				$anEvent['REAL_DATETIME'] =
				date("d.m.Y", $anEvent['UNIX_TIMESTAMP']);
			}

			$extendedEvents[] = $anEvent;
		}

		foreach ($extendedEvents as $key => $value) {
			$timestamp[$key] = $value['UNIX_TIMESTAMP'];
		}
		array_multisort($timestamp, $sortOrder, $extendedEvents);

		return $extendedEvents;
	}
}
?>